package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointOutside;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import xj.graph2d.DrawShape;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.graph.Geometry;
import xj.lang.CodeWriter;
import xj.util.gui.Point2DFloat;

public class PolygonShape 
  extends GraphNodeBase 
  implements PolyShape {

  public PolygonShape() {
    this(false, false);
  }

  public PolygonShape(boolean filled) {
    this(filled, false);
  }

  public PolygonShape(boolean filled, boolean createComp) {
    super(createComp);
    allowRotation = false;
    this.filled = filled;
    adapter = new PolyShapeAdapter(this, true);
  }

  public PolygonShape(float x1, float y1) {
    this(x1, y1, false, false);
  }

  public PolygonShape(float x1, float y1, boolean filled) {
    this(x1, y1, filled, false);
  }

  public PolygonShape(float x1, float y1, boolean filled, boolean createComp) {
    super(createComp);
    allowRotation = false;
    this.filled = filled;
    adapter = new PolyShapeAdapter(this, true);
    adapter.setFirst(x1, y1);
    setEnds(x1, y1, x1, y1);
  }

  @Override
public DrawShape cloneDrawShape(boolean deepClone) 
    throws CloneNotSupportedException {
    PolygonShape s = (PolygonShape) makeInstance();
    copyTo(s);
    cloneSubshapes(s, deepClone);
    cloneModel(s);
    copyPointsTo(s);
    return s;
  }

  public DrawShape makeInstance() {
    return new PolygonShape(filled, comp != null);
  }

  @Override
  public String getName() {
    return "Polygon";
  }

  @Override
  public boolean isEdgeSensitive() {
    return false;
  }

  @Override
  public boolean isCornorSensitive() {
    return false;
  }

  protected Point2D[] getBoundingPoints() {
    int n = adapter.getNumberOfPoints();
    Point2D[] result = new Point2D[n];
    float curx, cury;
    GeneralPath polygon = new GeneralPath(Path2D.WIND_EVEN_ODD, n);
    float x = adapter.getX();
    float y = adapter.getY();
    result[0] = new Point2DFloat(x, y);
    for (int i = 1; i < n; i++) {
      Point2D p = adapter.getPoint(i - 1);
      if (p != null) {
	curx = (float) p.getX() + x;
	cury = (float) p.getY() + y;
	result[i] = new Point2DFloat(curx, cury);
      }
    }
    return result;
  }

  protected GeneralPath makePolygon() {
    int n = adapter.getNumberOfPoints() - 1;
    float curx, cury;
    GeneralPath polygon = new GeneralPath(Path2D.WIND_EVEN_ODD, n);
    float x = adapter.getX();
    float y = adapter.getY();
    polygon.moveTo(x, y);
    for (int i = 0; i < n; i++) {
      Point2D p = adapter.getPoint(i);
      if (p != null) {
	curx = (float) p.getX() + x;
	cury = (float) p.getY() + y;
	polygon.lineTo(curx, cury);
      }
    }
    polygon.closePath();
    return polygon;
  }

  @Override
  public void draw(Graphics2D g2) {
    GeneralPath polygon = makePolygon();
    float x = (float) Math.min(p1.getX(), p2.getX());
    float y = (float) Math.min(p1.getY(), p2.getY());
    float w = (float) Math.abs(p1.getX() - p2.getX());
    float h = (float) Math.abs(p1.getY() - p2.getY());
    if (filled) {
      fillBackground(g2, x, y, w, h, polygon);
      if (showborder) {
	g2.draw(polygon);
      }
    } else {
      g2.draw(polygon);
    }
  }

  @Override
  public Shape getOutlineShape() {
    GeneralPath polygon = makePolygon();
    return polygon;
  }

  /* return the intersection point of p and the center of the node */
  @Override
  public Point2D intersect(Point2D p, float offsetX, float offsetY) {
    if (p != null) {
      Point2D result = null;
      Point2D[] pts = getBoundingPoints();
      if (pts != null) {
	int n = pts.length;
	Point2D c = getCenter();
	c.setLocation(c.getX() + offsetX, c.getY() + offsetY);
	if (isInside(p) == PointOutside) {
	  // find the intersection closer to p
	  result = Geometry.intersectionPolygonLine(pts, n, p, c);
	} else {
	  // find the intersection closer to c
	  result = Geometry.intersectionPolygonLine(pts, n, c, p);
	}
      }
      if (result != null) {
	return result;
      } else {
	return super.intersect(p, offsetX, offsetY);
      }
    }
    return null;
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("x", adapter.getX());
    attrs.put("y", adapter.getY());
    attrs.put("filled", filled);
    attrs.put("showBorder", showborder);
    return attrs;
  }

  @Override
  protected void writeXMLElements(CodeWriter out, int mode,
      String namespacePrefix) {
    if (out != null) {
      super.writeXMLElements(out, mode, namespacePrefix);

      writeXMLPoints(out, adapter.getPoints(), namespacePrefix);
    }
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String xs, ys;
      float x, y;
      xs = node.getAttribute("x");
      ys = node.getAttribute("y");
      try {
	x = Float.parseFloat(xs);
	y = Float.parseFloat(ys);
	adapter.setFirst(x, y);
      } catch (NumberFormatException ex) {
      }

      String s;
      s = node.getAttribute("filled");
      filled = Boolean.valueOf(s);
      s = node.getAttribute("showBorder");
      showborder = Boolean.valueOf(s);
    }
  }

  @Override
  protected void processXMLElement(org.w3c.dom.Element e, String namespaceURI) {
    if (e != null) {
      if ("Points".equals(e.getLocalName())) {
	adapter.setAttrFromXML(e, namespaceURI);
      } else {
	super.processXMLElement(e, namespaceURI);
      }
    }
  }

  /*
         * public void writeXML(CodeWriter out, int mode, String
         * namespacePrefix) { if (out != null) { String nodename =
         * getPrefixedName("PolygonShape", namespacePrefix); out.indentInc();
         * out.println("<" + nodename + " x=\"" + adapter.getX() + "\" y=\"" +
         * adapter.getY() + "\" filled=\"" + filled + "\" showBorder=\"" +
         * showborder + "\">"); writeXMLBase(out, mode, namespacePrefix);
         * writeXMLPoints(out, adapter.getPoints(), namespacePrefix);
         * out.thisIndentDec(); out.println("</" + nodename + ">"); } }
         * 
         * public void setAttrFromXML(org.w3c.dom.Element node, String
         * namespaceURI) { if (node != null) { String uri =
         * node.getNamespaceURI(); if (namespaceURI == null ||
         * namespaceURI.equals(uri)) { super.setAttrFromXML(node, namespaceURI);
         * 
         * String s; s = node.getAttribute("filled"); if (s != null &&
         * s.length() > 0) { try { this.filled =
         * Boolean.valueOf(s).booleanValue(); } catch (NumberFormatException ex) {} }
         * 
         * s = node.getAttribute("showBorder"); if (s != null && s.length() > 0) {
         * try { this.showborder = Boolean.valueOf(s).booleanValue(); } catch
         * (NumberFormatException ex) {} }
         * 
         * adapter.setAttrFromXML(node); } } }
         */

  // PolyShapeAdapter
  /** (x, y) is the starting, i.e. anchor, point of the polyshape */
  public float getX() {
    return adapter.getX();
  }

  public float getY() {
    return adapter.getY();
  }

  public void setFirst(float x, float y) {
    adapter.setFirst(x, y);
  }

  public void setStart(float x, float y) {
    adapter.setStart(x, y);
  }

  /** add a new point at the end, coincide with the current last point */
  public void addPoint() {
    adapter.addPoint();
  }

  /** add a new point at the end, (x, y) is absolute */
  public void addPoint(float x, float y) {
    adapter.addPoint(x, y);
  }

  /**
         * add a new point at the end. adj == true: (x, y) is absolute, needs to
         * be adjusted to be relative to the starting point adj == false: (x, y)
         * is relative to the starting point
         */
  public void addPoint(float x, float y, boolean adj) {
    adapter.addPoint(x, y, adj);
  }

  public void moveStartOnly(float dx, float dy) {
    adapter.moveStartOnly(dx, dy);
  }

  /** move the i-th vertex by (dx, dy) */
  public void moveVertex(int i, float dx, float dy) {
    adapter.moveVertex(i, dx, dy);
  }

  /** move the i-th vertex to (x, y), absolute coord */
  public void setPoint(int i, float x, float y) {
    adapter.setPoint(i, x, y);
  }

  public Point2D getPoint(int i) {
    return adapter.getPoint(i);
  }

  /** add a new point at the i-th position, (x, y) is absolute */
  public void addPoint(int i, float x, float y) {
    adapter.addPoint(i, x, y);
  }

  /**
         * add a new point at the i-th position. adj == true: (x, y) is
         * absolute, needs to be adjusted to be relative to the starting point
         * adj == false: (x, y) is relative to the starting point
         */
  public void addPoint(int i, float x, float y, boolean adj) {
    adapter.addPoint(i, x, y, adj);
  }

  public void removePoint(int i) {
    if (adapter.getNumberOfPoints() > 2) {
      adapter.removePoint(i);
    }
  }

  public void setLast(float x, float y) {
    adapter.setLast(x, y);
  }

  @Override
  public void scale(float sx, float sy) {
    adapter.scale(sx, sy);
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    adapter.setEndsAdj(x1, y1, x2, y2);
  }

  public void drawPolyLine(Graphics2D g2) {
    adapter.drawPolyLine(g2);
  }

  public Point2D getLast() {
    return adapter.getLast();
  }

  public int getNumberOfPoints() {
    return adapter.getNumberOfPoints();
  }

  @Override
  public void move(float dx, float dy) {
    adapter.move(dx, dy);
    super.move(dx, dy);
  }

  @Override
  public void drawFrame(Graphics2D g2) {
    adapter.drawFrame(g2);
  }

  @Override
  public int isInside(Point2D p) {
    return adapter.isInside(p);
  }

  @Override
  public int isInside(float px, float py) {
    return adapter.isInside(px, py);
  }

  @Override
  public void calculateBounds() {
    adapter.calculateBounds();
    super.calculateBounds();
  }

  public List getPoints() {
    return adapter.getPoints();
  }

  public void copyPointsTo(PolyShape s) {
    adapter.copyPointsTo(s);
  }

  public void scaleFirstLast(float x1, float y1, float x2, float y2) {
    scaleFirstLast(x1, y1, x2, y2, false);
  }

  public void scaleFirstLast(float x1, float y1, float x2, float y2,
			     boolean limit) {
    adapter.scaleFirstLast(x1, y1, x2, y2, null, limit);
  }

  @Override
  public String toString() {
    return (super.toString() + " " + adapter.toString());
  }

  protected PolyShapeAdapter adapter;

}
